R kui kalkulaator

Kõige lihtsam viis R-i kasutada on temaga lihtsalt arvutusi teha.

5 + 7 
[1] 12
25 ** 2 
[1] 625
1 / 2
[1] 0.5
1 / 0 
[1] Inf
0 / 0
[1] NaN
x = 5

ctrl alt i teeb uue koodi akna

shift enter paneb uue rea koodiblokist välja

ctrl enter jooksutab 1 rea

ctrl shift enter jooksutab terve koodibloki

Kohe on kasutajale kättesaadavad ka peamised matemaatilised funktsioonid ja konstandid.

2 + sqrt(375769) - 25^2 # astendamismärgina võib kasutada nii ^ kui ka **, nt 2**3 
[1] -10
sin(pi/6) + acosh(1) 
[1] 0.5
log(exp(1)) 
[1] 1
sqrt(-1+0i) 
[1] 0+1i
factorial(6) / choose(4, 2) 
[1] 120

Kasutatud matemaatilised funktsioonid on näited R-s defineeritud käskudest, mida kutsutakse välja nende nime ja järgnevate sulgudega. Sulgude sisse lisatakse funktsiooni argumendid. Argumendi nimed võib, kuid ei pea välja kirjutama, kui anda argumendi väärtused ette õiges järjekorras. Näiteks järgnevad käsud annavad sama tulemuse.

log(x = 25, base = 5) 
[1] 2
log(25, 5) 
[1] 2

Abi R-s

Tihtipeale ei pea kõiki argumente määrama, sest nendel on määratud vaikimisi väärtused. Selleks, et aru saada kuidas mõni funktsioon töötab tasub vaadata konkreetse funktsiooni abi lehekülgi. Neile saab ligi kirjutades funktsiooni ette ?. Näiteks.

?log

Abilehed tekivad kõrvalaknasse. Help failil on mitu standardset osa millest on erinevate küsimuste puhul abi.

Ülesanne 1

  • Mida teeb funktsioon rm?

  • Proovi läbi üks näide funktsioonist rm?

?rm
rm(x)

Muutujad

Loomulikult nagu programmeerimisekeelele kohane saab R-s defineerida ka muutujaid.

kaal = 80 
pikkus = 180
bmi = kaal / (pikkus / 100)^2 

bmi2 <- bmi**2

bmi
[1] 24.69136
jah = TRUE
ei = F

jah
[1] TRUE
ei
[1] FALSE

Muutujatele ei pea ette tüüpi, selle arvab R ise ära. Olulisemaid muutuja tüüpe ei ole väga palju:

Ühest tüübist teise teisendamiseks on mõeldud funktsioonid as.<tüübi nimi>. Näiteks saab tihti veateateid, kui tehteid tehakse valest tüübist muutujatega.

"5" + 5
Error in "5" + 5 : non-numeric argument to binary operator

Kasutades tüübiteisendusi saab neist üle.

as.numeric("5") + 5
[1] 10

Ülesanne 2

  • Kas tõeväärtusi saab liita?

  • Milline on TRUE numbriline väärtus?

TRUE + TRUE
[1] 2
TRUE - TRUE
[1] 0
TRUE - FALSE
[1] 1
F - T
[1] -1
FALSE + FALSE
[1] 0
FALSE - FALSE
[1] 0

Funktsioonide defineerimine

Me enne nägime, et on võimalik kasutada ette antud funktsioone. Kuid neid on ka lihtne defineerida järgneva süntaksiga.

<funktsiooni nimi> = function(<argumentide nimekiri>){ 
     <argumentidega tehtavad operatsioonid> 
     return(<tagastatav väärtus>) 
} 

Näiteks võime defineerida kehamassi indeksi arvutamise funktsiooni.

bmi = function(kaal, pikkus = 180){
  res = kaal / (pikkus / 100) ** 2
  
  return(res)
}

bmi(85)
[1] 26.23457
bmi(85, 195)
[1] 22.35371

Lisapaketid

Paljud R-i lisafunktsioonid on pakendatud pakettidesse, mis tuleb kasutamiseks eraldi laadida käsuga library. Näiteks funktsioon as_date on lisapaketis lubridate, mille peab enne kasutamist sisse lugema.

#as_date("2012-01-01")

library(lubridate) 

as_date("2012-01-01")
[1] "2012-01-01"

Enamus pakette mis asuvad keskses repositooriumis CRAN-s, saab R-s installida kas käsuga install.packages konsoolilt või Packages saki alt paremal alumises aknas RStudios (kui vaja valige Install from: Repository (CRAN)).

Ülesanne 3

  • Installi pakett tidyverse ja loe see sisse.
library(tidyverse)

Vektorid

Keskne objektitüüp R-s on vektor (sarnane 1D numpy array-ga). mis on teisisõnu ühe mõõtmeline sama tüüpi muutujate järjestus. Vektorite loomiseks on mitmeid viise.

1:10 # Järjest numbrid 
 [1]  1  2  3  4  5  6  7  8  9 10
9:2 # Tagurpidi järjestus
[1] 9 8 7 6 5 4 3 2
c(1, 4, 2, 6) # Suvaliste elementide järjestus
[1] 1 4 2 6
c(1:10, 4, c(2, 4)) # Argumendid võivad olla nii vektori kui üksikud väärtused
 [1]  1  2  3  4  5  6  7  8  9 10  4  2  4
c("A", "B", "C") # Vektorisse võib panna ka sõnesid
[1] "A" "B" "C"
seq(0, 1, length.out = 10) # Suvalise algus- ja lõpppunktiga võrdse vahemikuga jadad
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 0.8888889 1.0000000
rep(1:2, times = 5)
 [1] 1 2 1 2 1 2 1 2 1 2

fnc “c” tähendab combine!!! c(1, 2) loob 1d array (vektori) [1, 2]

Andmete eraldamine vektorist käib kandiliste sulgudega. Tasub meelde jätta et R-s algab indekseerimine 1-st.

x = 10:1
x[1]
[1] 10
x[1:5]
[1] 10  9  8  7  6

Tegelikult käsitletakse R-s isegi ühe väärtusega objekte vektoritena. Seega pole suurt vahet kas tehetes on vektor või üksikud väärtused. Vektori puhul tehakse tehteid elemendi kaupa. Kui sisendiks on erineva pikkusega vektorid siis lühemat korratakse nii kaua kui saaab pikemaga võrdseks. Sama loogika kehtib nii aritmeetiliste tehete kui ka paljude funktsioonide puhul.

bmi(c(85, 90, 95, 100, 105), 194)
[1] 22.58476 23.91327 25.24179 26.57031 27.89882

Paljude funktsioonide puhul muidugi oodataksegi vektorit ja tulemuseks on arv.

min(1:10)
[1] 1
max(1:10)
[1] 10
mean(1:10)
[1] 5.5
median(0:10)
[1] 5

Ülesanne 4

  • Defineeri funktsioon mis teisendab etteantud numbrilise vektori 0 ja 1 vahele, nii et minimaalne väärtus on 0 ja maksimaalne 1. Vihje: vektori x ja selle elemendi xi korral on valem järgmine (xi - min(x))/(max(x) -min(x)).

  • Testi saadud funktsiooni, vektorite 0:10 ja -5:5 peal. Kas tulemused on sarnased või erinevad?


vektor_teisenda = function(vektor) {
  
  vastus =(vektor - min(vektor)) / (max(vektor) - min(vektor))
  
  return(vastus)
}

vektor_teisenda(0:10)
 [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Andmetabelid

Praktilise andmeanalüüsi puhul on andmed antud tabelites, kus veergudes on erinevat tüüpi tunnused. Säärane tabel on olulisel kohal ka R-s kus ajalooliselt on kasutatud data.frame nimelist objekti ja viimasel ajal rohkem tibble nimelist data.frame edasiarendust tidyverse paketist. Sellisest tabelist võib mõelda kui tunnusvektorite kogumist, kus kõik tunnusvektorid peavad olema ühepikkused.

library(tidyverse) # peab tegema vaid korra sessiooni alguses

moodud = tibble(
  Nimi = c("Mari", "Jaana", "Peeter"),
  Pikkus = c(155, 165, 190), 
  Kaal = c(50, 60, 100), 
  Sugu = c("N", "N", "M")
)

moodud

Andmetabeli sisse lugemine erinevatest allikatest

Tekstifailid

Andmetabeleid hoitakse enamasti tekstifailidena, kus veerud on kas tabulaatori, koma või semikooloniga eraldatud. Selliste failide lugemiseks saab kasutada read_delim käsku, kus tuleb kindlasti määrata failimi ja veergude eraldaja.

mass = read_delim("massachusetts.csv", delim = ",")
mass
mass = read_delim("massachusetts.csv", delim = "\t")
mass

Kuna enamus faile on eraldatud kas tabulaatori või komaga, siis nende failide jaoks on ka shortcutid read_tsv ja read_csv.

mass = read_csv("massachusetts.csv")

mass

Kirjeldatud käsud teevad üldiselt päris head tööd, kuid siiski on failidel tihti omapärasid, näiteks on puuduvad väärtused imelikult märgitud, muutuja loetakse sisse vale tüübiga, jne. Nende probleemidega on võimalik tegeleda enamasti juba read_* käsu sees rakendades erinevaid parameetreid. Tasub lugeda nende funktsioonid abilehekülgi ja otsida näiteid kui hätta jääb.

Ülesanne 5

  • Loe sisse andmestik failist massachusetts.txt (see asub samas kataloogis kui praktikumi notebook).

mass_txt = read_tsv("massachusetts.txt")

mass_txt

RData

R tabelite salvestamine tekstifailidena ning nede hiljem sisselugemine võib põhjustada vigu. Kui mõni fail luuakse R-s ja hiljem ka R-s edasi töödeldakse on kasulik ta salvestada binaarse R objektina. Nii on teda väga lihtne hiljem sisse lugeda. Seda on võimalik saavutada käskudega save ja load. Käsule save võib ette anda ka mitu objekti.

x = 1
save(x, mass, file = "objects.RData")

Salvestatud objekti saab sisse lugeda käsuga load. Vaikimisi tekivad töökeskkonda sama nimega objektid nagu sai salvestatud. Et aru saada mis täpselt sisse loeti, tasub kasutada parameetrit verbose.

load("objects.RData", verbose = T)
Loading objects:
  x
  mass

Andmetabelite töötlemine tidyverse käskudega

R-s on mitmeid viise kuidas andmeid saab töödelda, viimasel ajal on väga populaarseks saanud lähenemine mis on implementeeritud pakettide kogumikus ühise nimetajaga tidyverse. Võiks öelda, et tegu on lausa omamoodi alamkeelega R-i sees, mis laenab kontseptsioone erinveatest keeltest nagu SQL ja bash ning proovib paljud operatsioonid panna tööle ühiste printsiipide põhjal.

%>%

Üheks tähtsamaks käsuks tidyverse puhul on nn “toru” %>% mis võimaldab kirjutada pikkasid käskude jadasid loetavalt. Illustreerimaks selle kasulikkust vaatame järgmist näidet.

prices = c("$1423.55", "$556.98", "$4321.99", "$657.01")

prices_trim = str_replace(prices, "\\$", "")
prices_trim_num = as.numeric(prices_trim) 
prices_trim_num_round = round(prices_trim_num, digits = 0)
prices_round_final = str_c("$", prices_trim_num_round)
prices_round_final
[1] "$1424" "$557"  "$4322" "$657" 

Siin rakendame järjest mitmeid operatsioone ja salvestame kõik vahemuutujatesse millele peame nimesid mõtlema, mis on suhteliselt tüütu. Me võime sellise operatsioooni kirjutada ka ühel real.

str_c("$", round(as.numeric(str_replace(prices, "\\$", "")), digits = 0))
[1] "$1424" "$557"  "$4322" "$657" 

Kuid sellist asja on väga raske lugeda. Sest funktsioone rakendatakse järjest seestpooolt väljapoole. Palju lihtsam oleks lugeda, kui funktsioonid liiguks rakendamise järjekorras vasakult paremale. See on just see mida operaator %>% teeb. Ta võimaldab kirjutada f(g(x)), kui x %>% g() %>% f(). Meie eelmine näide näeks välja järgnev.

prices %>% str_replace("\\$", "") %>% as.numeric() %>% round() %>% str_c("$", .)
[1] "$1424" "$557"  "$4322" "$657" 

Pane tähele, et kui eelneva funktsiooni väljund peaks olema järgmises funktsiooni väljakutses esimesel positsioonil, siis võib selle ära jätta. Kui väljund peab minema mõnele teisele positsioonile, siis saab seda märkida kui . (vt viimane käsk).

Tidyverse funktsioonid

Tidyverse on üles ehitatud funktsioonidel, millest igaüks võtab sisse andmetabeli ja annab välja modifitseeritud andmetabeli. Iga funktsioon teostab ainult ühte operatsiooni, aga kui neid operaatoriga %>% järjest rakendada on võimalik saavutada väga palju. Peamised funktsioonid tidyverse pakettides on järgnevad:

  • select - võimaldab valida andmetabeli veerge

  • filter - võimaldab valida andmetabeli ridu

  • mutate - võimaldab tekitada tabelisse uusi veergusid või modifitseerida vanu

  • group_by ja summarize - võimaldavad summeerida väärtusi tunnuste poolt defineeritud gruppides

  • arrange - võimaldab sorteerida tabelit ühe või mitme tunnuse järgi

Järgnevalt vaatame iga funktsiooni ja mõnda kasutusnäidet täpsemalt kasutades andmestikku mass, mis sai sisse loetud eelnevalt.

select

select võimaldab andmetabeli veerge valida ja neid ka selle käigus ümber nimetada. Veergude nimed saab funktsioonile ette anda ilma jutumärkideta.

mass %>% select(AGEP, SEX)
mass %>% select(Age = AGEP, Gender = SEX) # paneme ilusamad nimed
mass %>% select(-CIT, -AGEP) # Saame ka konkreetseid veerge välja visata

filter

filter võimaldab ridu filtreerida seades loogilisi tingimusi veergudele. Sisendtabelis olevate veergude nimed tunneb filter automaatselt ära.

mass %>% filter(AGEP < 20) # Vanus väiksem kui 20
mass %>% filter(CIT == "Not a citizen of the U.S.")
mass %>% filter(
  CIT 
    %in% 
      c(
        "U.S. citizen by naturalization", 
        "Not a citizen of the U.S."
      )
  )
mass %>% filter(AGEP < 20 | CIT == "Not a citizen of the U.S.") 

mutate

mutate võimaldab luua uusi veeraid vastavalt sellele kas veerg millele väärtus omistatakse juba eksiteerib või mitte. Nagu eelnevatelgi funktsioge või muuta olemasolevonidel mutate sees saab tehetel kasutada veerunimesid.

mass %>% mutate(Old = AGEP > 60) %>% select(AGEP, Old)
mass %>% mutate(AGEP = AGEP + 1) 

summarize

summarize on käsk mis võimaldab andmestikul kokkuvõtteid välja arvutada. Erinevalt mutate käsust tagastab ta ainult väljaarvutatud suurused ja mitte midagi muud.

mass %>% summarize(MeanAge = mean(AGEP))
mass %>% summarize(MeanAge = mean(AGEP), MinAge = min(AGEP))

Funktsioon n() summarize sees ütleb kui mitu rida sisend tabelis on.

mass %>% summarize(MeanAge = mean(AGEP), MinAge = min(AGEP), N = n())
mass %>% 
  summarize(
    MeanAge = mean(AGEP), 
    MinAge = min(AGEP), 
    MaxAge = max(AGEP), 
    N = n()
  );

group_by

group_by võimaldab rakendada nn “split-apply-combine”strateegiat, kus andmestik tükeldatakse ühe või mitme tunnuse väärtus alusel alamandmestikeks, rakendatakse mingit funktsiooni alam-andmestikel ning tulemused kombineeritakse. Kui me oleme rakendanud käsku group_by andmestikule siis järgnevate käskude puhul just selline operatsioon toimubki.

group_by ja summarize moodustavad kombinatsiooni, millega on võimalik tunnuste poolt defineeritud gruppide kaupa summeerida teiste muutujate väärtuseid. Tulemusse jäävad alles tunnused mille järgi grupeeriti ning summarize käsus defineeeritud tunnused. Grupeerida võib nii ühe kui mitme tunnuse järgi.

mass %>% group_by(CIT) %>% summarize(AGEP = mean(AGEP))
mass %>% group_by(CIT, SEX) %>% summarize(AGEP = mean(AGEP))

Funktsioon n() summarize sees ütleb kui mitu tabeli rida konkreetsele grupeerivate tunnuste kombinatsioonile vastab. See on väga kasulik sagedustabelite tegemisel

mass %>% group_by(CIT) %>% summarize(N = n())
mass %>% group_by(CIT, SEX) %>% summarize(AGEP = mean(AGEP), N = n())

Kui pärast group_by käsku rakenda mutate käsku. Siis mutate töötab jupikaupa group_by defineeritud alamhulkadel. Näiteks nii saame lisada igale reale grupikeskmise või järjekorra numbri grupi sees.

mass %>% select(AGEP, SEX) %>% group_by(SEX) %>% mutate(MeanAgeGroup = mean(AGEP))
mass %>% select(AGEP, SEX) %>% group_by(SEX) %>% mutate(IdInGroup = 1:n())

arrange

arrange lihtsalt sorteerib andmetabeli etteantud tunnus(t)e järgi.

mass %>% arrange(AGEP) # Vaikimise väiksemast suuremaks
mass %>% select(AGEP, SEX) %>% arrange(AGEP, SEX)
# mass %>% arrange(desc(AGEP)) # Käsuga desc saab sorteerimise suuna ümber pöörata

mass %>% arrange(-AGEP) # Käsuga desc saab sorteerimise suuna ümber pöörata

Funktsioonide kombineerimine

Olgugi, et iga funktsioon üksi teostab konkreetse lihtsa operatsiooni, võib neid üksteise järel ritta ladudes lahendada päris keerukaid probleeme. Näiteks siin leiame top 5 ametit tööealistele USAs ja väljaspool sündinud inimestele.

mass %>% 
  select(Age = AGEP, Citizenship = CIT, Occupation = OCCP) %>% 
  filter(Age > 18 & Age < 65) %>% 
  mutate(BornUS = Citizenship == "Born in the U.S.") %>% 
  group_by(BornUS, Occupation) %>% 
  summarize(N = n()) %>% 
  group_by(BornUS) %>% 
  mutate(Rank = rank(-N)) %>% 
  filter(Rank <= 5) %>% 
  arrange(BornUS, Rank)

Ülesanded 6

  • Kummal on kõrgem keskmine palk kas üle või alla 55 aastastel arstidel? (Kasuta tunnuseid WAGP - palk, AGEP - vanus, OCCP - amet, kategooria “MED-PHYSICIANS AND SURGEONS”, W)

  • Millise ameteid esindavad naised töötavad keskmiselt nädalas kõige kauem? Keskendu ainult ametitele, mida esindavaid naisi on andmestikus vähemal 10. (Kasuta tunnuseid AGEP - vanus, SEX - sugu väärtus “Female” , WKHP - nädalas tehtud töötunnid, OCCP - amet)

  • Mis on täiskasvanud inimeste kõige kõrgema keskmise palgaga ametid Massachusettsi andmestiku kohaselt? Keskendu ainult ametitele, mille esindajaid on andmestikus vähemal 10. (Kasuta tunnuseid AGEP - vanus, WAGP - palk, OCCP - amet)


mass %>% 
  select(Age = AGEP, Occupation = OCCP, Wage = WAGP) %>% 
  filter(Occupation == "MED-PHYSICIANS AND SURGEONS") %>% 
  mutate(isOver55 = Age >= 55) %>% 
  group_by(isOver55, Occupation) %>% 
  summarise(MeanWage = mean(Wage))

mass %>% 
  select(sex = SEX, occupation = OCCP, workHoursPerWeek = WKHP) %>%
  filter(sex == "Female") %>% 
  group_by(occupation) %>%
  filter(n() >= 10) %>%
  summarise(meanHours = mean(workHoursPerWeek)) %>% # mean(workHoursPerWeek, na.rm = TRUE)
  arrange(-meanHours) %>%
  select(occupation, meanHours)
mass %>% 
  filter(SEX == "Female") %>% 
  group_by(OCCP) %>% 
  filter(n() >= 10) %>% 
  summarise(
    mean_hours = mean(WKHP, na.rm = TRUE),
    count = n()
  ) %>% 
  arrange(desc(mean_hours))

Lisamaterjale

LS0tDQp0aXRsZTogIlByYWt0aWt1bSA1IC0gUiBhbHVzZWQiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyBSIGt1aSBrYWxrdWxhYXRvcg0KDQpLw7VpZ2UgbGlodHNhbSB2aWlzIFItaSBrYXN1dGFkYSBvbiB0ZW1hZ2EgbGlodHNhbHQgYXJ2dXR1c2kgdGVoYS4NCg0KYGBge3J9DQo1ICsgNyANCjI1ICoqIDIgDQoxIC8gMg0KMSAvIDAgDQowIC8gMA0KDQp4ID0gNQ0KYGBgDQoNCmBgYHtyfQ0KDQpgYGANCg0KY3RybCBhbHQgaSB0ZWViIHV1ZSBrb29kaSBha25hDQoNCnNoaWZ0IGVudGVyIHBhbmViIHV1ZSByZWEga29vZGlibG9raXN0IHbDpGxqYQ0KDQpjdHJsIGVudGVyIGpvb2tzdXRhYiAxIHJlYQ0KDQpjdHJsIHNoaWZ0IGVudGVyIGpvb2tzdXRhYiB0ZXJ2ZSBrb29kaWJsb2tpDQoNCktvaGUgb24ga2FzdXRhamFsZSBrw6R0dGVzYWFkYXZhZCBrYSBwZWFtaXNlZCBtYXRlbWFhdGlsaXNlZCBmdW5rdHNpb29uaWQgamEga29uc3RhbmRpZC4NCg0KYGBge3J9DQoyICsgc3FydCgzNzU3NjkpIC0gMjVeMiAjIGFzdGVuZGFtaXNtw6RyZ2luYSB2w7VpYiBrYXN1dGFkYSBuaWkgXiBrdWkga2EgKiosIG50IDIqKjMgDQpzaW4ocGkvNikgKyBhY29zaCgxKSANCmxvZyhleHAoMSkpIA0Kc3FydCgtMSswaSkgDQpmYWN0b3JpYWwoNikgLyBjaG9vc2UoNCwgMikgDQpgYGANCg0KS2FzdXRhdHVkIG1hdGVtYWF0aWxpc2VkIGZ1bmt0c2lvb25pZCBvbiBuw6RpdGVkIFItcyBkZWZpbmVlcml0dWQga8Okc2t1ZGVzdCwgbWlkYSBrdXRzdXRha3NlIHbDpGxqYSBuZW5kZSBuaW1lIGphIGrDpHJnbmV2YXRlIHN1bGd1ZGVnYS4gU3VsZ3VkZSBzaXNzZSBsaXNhdGFrc2UgZnVua3RzaW9vbmkgYXJndW1lbmRpZC4gQXJndW1lbmRpIG5pbWVkIHbDtWliLCBrdWlkIGVpIHBlYSB2w6RsamEga2lyanV0YW1hLCBrdWkgYW5kYSBhcmd1bWVuZGkgdsOkw6RydHVzZWQgZXR0ZSDDtWlnZXMgasOkcmpla29ycmFzLiBOw6RpdGVrcyBqw6RyZ25ldmFkIGvDpHN1ZCBhbm5hdmFkIHNhbWEgdHVsZW11c2UuDQoNCmBgYHtyfQ0KbG9nKHggPSAyNSwgYmFzZSA9IDUpIA0KbG9nKDI1LCA1KSANCmBgYA0KDQojIyBBYmkgUi1zDQoNClRpaHRpcGVhbGUgZWkgcGVhIGvDtWlraSBhcmd1bWVudGUgbcOkw6RyYW1hLCBzZXN0IG5lbmRlbCBvbiBtw6TDpHJhdHVkIHZhaWtpbWlzaSB2w6TDpHJ0dXNlZC4gU2VsbGVrcywgZXQgYXJ1IHNhYWRhIGt1aWRhcyBtw7VuaSBmdW5rdHNpb29uIHTDtsO2dGFiIHRhc3ViIHZhYWRhdGEga29ua3JlZXRzZSBmdW5rdHNpb29uaSBhYmkgbGVoZWvDvGxnaS4gTmVpbGUgc2FhYiBsaWdpIGtpcmp1dGFkZXMgZnVua3RzaW9vbmkgZXR0ZSBgP2AuIE7DpGl0ZWtzLg0KDQpgYGB7cn0NCj9sb2cNCmBgYA0KDQpBYmlsZWhlZCB0ZWtpdmFkIGvDtXJ2YWxha25hc3NlLiBIZWxwIGZhaWxpbCBvbiBtaXR1IHN0YW5kYXJkc2V0IG9zYSBtaWxsZXN0IG9uIGVyaW5ldmF0ZSBrw7xzaW11c3RlIHB1aHVsIGFiaS4NCg0KLSAgICoqVXNhZ2U6Kiogc2lpbiBvbiBmdW5rdHNpb29uaSB2w6RsamFrdXRzZSBrb29zIGvDtWlnaSBwYXJhbWVldHJpdGVnYS4gSnVodWwga3VpIGtvbmtyZWV0c2VsZSBwYXJhbWVldHJpbGUgb24gZGVmaW5lZXJpdHVkIHZhaWtldsOkw6RydHVzLCBzaWlzIG9uIHNlZSBraXJqYXMgcGFyYW1lZXRyaSBqw6RyZWwgcMOkcmFzdCB2w7VyZHVzbcOkcmtpLg0KDQotICAgKipBcmd1bWVudHM6Kioga2lyamVsZGFiIHBhcmFtZWV0cmkgdMOkaGVuZHVzdC4NCg0KLSAgICoqRGV0YWlsczoqKiBhbm5hYiBmdW5rdHNpb29uaSBpbXBsZW1lbnRhdHNpb29uaSBkZXRhaWxpZC4NCg0KLSAgICoqRXhhbXBsZXM6KiogbsOkaXRlZCBtaWRhIMO8bGRpc2VsdCB2w7VpYiBrb2hlIGzDpGJpIGpvb2tzdXRhZGEuICoqVXVlIGZ1bmt0c2lvb25pIHR1bmRtYSDDtXBwaW1pc2VsIG9uIG7DpGlkZXRlIHNla3RzaW9vbiB0aWh0aXBlYWxlIGvDtWlnZSBzdXVyZW1ha3MgYWJpa3MqKiwgc2VzdCBuaWkgb24gbGlodHNhbWEgc2FhZGEgYXJ1IHNpc2VuZGkgamEgdsOkbGp1bmRpIG9vZGF0YXZhc3Qga3VqdXN0Lg0KDQojIyMgw5xsZXNhbm5lIDENCg0KLSAgIE1pZGEgdGVlYiBmdW5rdHNpb29uIGBybWA/DQoNCi0gICBQcm9vdmkgbMOkYmkgw7xrcyBuw6RpZGUgZnVua3RzaW9vbmlzdCBgcm1gPw0KDQpgYGB7cn0NCj9ybQ0KDQpgYGANCg0KYGBge3J9DQpybSh4KQ0KYGBgDQoNCiMjIE11dXR1amFkDQoNCkxvb211bGlrdWx0IG5hZ3UgcHJvZ3JhbW1lZXJpbWlzZWtlZWxlbGUga29oYW5lIHNhYWIgUi1zIGRlZmluZWVyaWRhIGthIG11dXR1amFpZC4NCg0KYGBge3J9DQprYWFsID0gODAgDQpwaWtrdXMgPSAxODANCmJtaSA9IGthYWwgLyAocGlra3VzIC8gMTAwKV4yIA0KDQpibWkyIDwtIGJtaSoqMg0KDQpibWkNCg0KamFoID0gVFJVRQ0KZWkgPSBGDQoNCmphaA0KZWkNCmBgYA0KDQpNdXV0dWphdGVsZSBlaSBwZWEgZXR0ZSB0w7zDvHBpLCBzZWxsZSBhcnZhYiBSIGlzZSDDpHJhLiBPbHVsaXNlbWFpZCBtdXV0dWphIHTDvMO8cGUgZWkgb2xlIHbDpGdhIHBhbGp1Og0KDQotICAgYGxvZ2ljYWxgIC0gdMO1ZXbDpMOkcnR1c3RlIGhvaWRtaXNla3MgKHbDpMOkcnR1c2VkIGBUUlVFYC9gRkFMU0VgKSwNCg0KLSAgIGBudW1lcmljYCAtIGvDtWlrIHJlYWFsYXJ2dWQsIHNlYWxodWxnYXMgdMOkaXNhcnZ1ZCwNCg0KLSAgIGBjaGFyYWN0ZXJgIC0gc8O1bmVkIChzaXNlc3RhbWlzZWwgcGFubmEganV0dW3DpHJraWRlIHZhaGVsZSkuDQoNCsOcaGVzdCB0w7zDvGJpc3QgdGVpc2UgdGVpc2VuZGFtaXNla3Mgb24gbcO1ZWxkdWQgZnVua3RzaW9vbmlkIGBhcy48dMO8w7xiaSBuaW1pPmAuIE7DpGl0ZWtzIHNhYWIgdGlodGkgdmVhdGVhdGVpZCwga3VpIHRlaHRlaWQgdGVoYWtzZSB2YWxlc3QgdMO8w7xiaXN0IG11dXR1amF0ZWdhLg0KDQpgYGB7cn0NCiI1IiArIDUNCmBgYA0KDQpLYXN1dGFkZXMgdMO8w7xiaXRlaXNlbmR1c2kgc2FhYiBuZWlzdCDDvGxlLg0KDQpgYGB7cn0NCmFzLm51bWVyaWMoIjUiKSArIDUNCmBgYA0KDQojIyMgw5xsZXNhbm5lIDINCg0KLSAgIEthcyB0w7VldsOkw6RydHVzaSBzYWFiIGxpaXRhPw0KDQotICAgTWlsbGluZSBvbiBUUlVFIG51bWJyaWxpbmUgdsOkw6RydHVzPw0KDQpgYGB7cn0NClRSVUUgKyBUUlVFDQpUUlVFIC0gVFJVRQ0KDQpUUlVFIC0gRkFMU0UNCkYgLSBUDQoNCkZBTFNFICsgRkFMU0UNCkZBTFNFIC0gRkFMU0UNCmBgYA0KDQojIyBGdW5rdHNpb29uaWRlIGRlZmluZWVyaW1pbmUNCg0KTWUgZW5uZSBuw6RnaW1lLCBldCBvbiB2w7VpbWFsaWsga2FzdXRhZGEgZXR0ZSBhbnR1ZCBmdW5rdHNpb29uZS4gS3VpZCBuZWlkIG9uIGthIGxpaHRuZSBkZWZpbmVlcmlkYSBqw6RyZ25ldmEgc8O8bnRha3NpZ2EuDQoNCmBgYCByDQo8ZnVua3RzaW9vbmkgbmltaT4gPSBmdW5jdGlvbig8YXJndW1lbnRpZGUgbmltZWtpcmk+KXsgDQogICAgIDxhcmd1bWVudGlkZWdhIHRlaHRhdmFkIG9wZXJhdHNpb29uaWQ+IA0KICAgICByZXR1cm4oPHRhZ2FzdGF0YXYgdsOkw6RydHVzPikgDQp9IA0KYGBgDQoNCk7DpGl0ZWtzIHbDtWltZSBkZWZpbmVlcmlkYSBrZWhhbWFzc2kgaW5kZWtzaSBhcnZ1dGFtaXNlIGZ1bmt0c2lvb25pLg0KDQpgYGB7cn0NCmJtaSA9IGZ1bmN0aW9uKGthYWwsIHBpa2t1cyA9IDE4MCl7DQogIHJlcyA9IGthYWwgLyAocGlra3VzIC8gMTAwKSAqKiAyDQogIA0KICByZXR1cm4ocmVzKQ0KfQ0KDQpibWkoODUpDQpibWkoODUsIDE5NSkNCmBgYA0KDQojIyBMaXNhcGFrZXRpZA0KDQpQYWxqdWQgUi1pIGxpc2FmdW5rdHNpb29uaWQgb24gcGFrZW5kYXR1ZCBwYWtldHRpZGVzc2UsIG1pcyB0dWxlYiBrYXN1dGFtaXNla3MgZXJhbGRpIGxhYWRpZGEga8Okc3VnYSBgbGlicmFyeWAuIE7DpGl0ZWtzIGZ1bmt0c2lvb24gYGFzX2RhdGVgIG9uIGxpc2FwYWtldGlzIGBsdWJyaWRhdGVgLCBtaWxsZSBwZWFiIGVubmUga2FzdXRhbWlzdCBzaXNzZSBsdWdlbWEuDQoNCmBgYHtyfQ0KI2FzX2RhdGUoIjIwMTItMDEtMDEiKQ0KDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgDQoNCmFzX2RhdGUoIjIwMTItMDEtMDEiKQ0KYGBgDQoNCkVuYW11cyBwYWtldHRlIG1pcyBhc3V2YWQga2Vza3NlcyByZXBvc2l0b29yaXVtaXMgQ1JBTi1zLCBzYWFiIFItcyBpbnN0YWxsaWRhIGthcyBrw6RzdWdhIGBpbnN0YWxsLnBhY2thZ2VzYCBrb25zb29saWx0IHbDtWkgUGFja2FnZXMgc2FraSBhbHQgcGFyZW1hbCBhbHVtaXNlcyBha25hcyBSU3R1ZGlvcyAoa3VpIHZhamEgdmFsaWdlIEluc3RhbGwgZnJvbTogUmVwb3NpdG9yeSAoQ1JBTikpLg0KDQojIyMgw5xsZXNhbm5lIDMNCg0KLSAgIEluc3RhbGxpIHBha2V0dCBgdGlkeXZlcnNlYCBqYSBsb2Ugc2VlIHNpc3NlLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiMjIFZla3RvcmlkDQoNCktlc2tuZSBvYmpla3RpdMO8w7xwIFItcyBvbiB2ZWt0b3IgKHNhcm5hbmUgMUQgYG51bXB5IGFycmF5YC1nYSkuIG1pcyBvbiB0ZWlzaXPDtW51IMO8aGUgbcO1w7V0bWVsaW5lIHNhbWEgdMO8w7xwaSBtdXV0dWphdGUgasOkcmplc3R1cy4gVmVrdG9yaXRlIGxvb21pc2VrcyBvbiBtaXRtZWlkIHZpaXNlLg0KDQpgYGB7cn0NCjE6MTAgIyBKw6RyamVzdCBudW1icmlkIA0KOToyICMgVGFndXJwaWRpIGrDpHJqZXN0dXMNCmMoMSwgNCwgMiwgNikgIyBTdXZhbGlzdGUgZWxlbWVudGlkZSBqw6RyamVzdHVzDQpjKDE6MTAsIDQsIGMoMiwgNCkpICMgQXJndW1lbmRpZCB2w7VpdmFkIG9sbGEgbmlpIHZla3Rvcmkga3VpIMO8a3Npa3VkIHbDpMOkcnR1c2VkDQpjKCJBIiwgIkIiLCAiQyIpICMgVmVrdG9yaXNzZSB2w7VpYiBwYW5uYSBrYSBzw7VuZXNpZA0Kc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMCkgIyBTdXZhbGlzZSBhbGd1cy0gamEgbMO1cHBwdW5rdGlnYSB2w7VyZHNlIHZhaGVtaWt1Z2EgamFkYWQNCnJlcCgxOjIsIHRpbWVzID0gNSkNCmBgYA0KDQpmbmMgImMiIHTDpGhlbmRhYiBjb21iaW5lISEhIGMoMSwgMikgbG9vYiAxZCBhcnJheSAodmVrdG9yaSkgWzEsIDJdDQoNCkFuZG1ldGUgZXJhbGRhbWluZSB2ZWt0b3Jpc3Qga8OkaWIga2FuZGlsaXN0ZSBzdWxndWRlZ2EuIFRhc3ViIG1lZWxkZSBqw6R0dGEgZXQgKipSLXMgYWxnYWIgaW5kZWtzZWVyaW1pbmUgMS1zdC4qKg0KDQpgYGB7cn0NCnggPSAxMDoxDQp4WzFdDQp4WzE6NV0NCmBgYA0KDQpUZWdlbGlrdWx0IGvDpHNpdGxldGFrc2UgUi1zIGlzZWdpIMO8aGUgdsOkw6RydHVzZWdhIG9iamVrdGUgdmVrdG9yaXRlbmEuIFNlZWdhIHBvbGUgc3V1cnQgdmFoZXQga2FzIHRlaGV0ZXMgb24gdmVrdG9yIHbDtWkgw7xrc2lrdWQgdsOkw6RydHVzZWQuIFZla3RvcmkgcHVodWwgdGVoYWtzZSB0ZWh0ZWlkIGVsZW1lbmRpIGthdXBhLiBLdWkgc2lzZW5kaWtzIG9uIGVyaW5ldmEgcGlra3VzZWdhIHZla3RvcmlkIHNpaXMgbMO8aGVtYXQga29ycmF0YWtzZSBuaWkga2F1YSBrdWkgc2FhYWIgcGlrZW1hZ2EgdsO1cmRzZWtzLiBTYW1hIGxvb2dpa2Ega2VodGliIG5paSBhcml0bWVldGlsaXN0ZSB0ZWhldGUga3VpIGthIHBhbGp1ZGUgZnVua3RzaW9vbmlkZSBwdWh1bC4NCg0KYGBge3J9DQoxOjEwICsgNSANCjE6MTAgKyAxMDoxDQpsb2coYygxLCAxMCwgMTAwLCAxMDAwKSwgYmFzZSA9IDEwKQ0KYm1pKGMoODUsIDkwLCA5NSwgMTAwLCAxMDUpLCAxOTQpDQpgYGANCg0KUGFsanVkZSBmdW5rdHNpb29uaWRlIHB1aHVsIG11aWR1Z2kgb29kYXRha3NlZ2kgdmVrdG9yaXQgamEgdHVsZW11c2VrcyBvbiBhcnYuDQoNCmBgYHtyfQ0KbWluKDE6MTApDQptYXgoMToxMCkNCm1lYW4oMToxMCkNCm1lZGlhbigwOjEwKQ0KYGBgDQoNCiMjIyDDnGxlc2FubmUgNA0KDQotICAgRGVmaW5lZXJpIGZ1bmt0c2lvb24gbWlzIHRlaXNlbmRhYiBldHRlYW50dWQgbnVtYnJpbGlzZSB2ZWt0b3JpIDAgamEgMSB2YWhlbGUsIG5paSBldCBtaW5pbWFhbG5lIHbDpMOkcnR1cyBvbiAwIGphIG1ha3NpbWFhbG5lIDEuIFZpaGplOiB2ZWt0b3JpICp4KiBqYSBzZWxsZSBlbGVtZW5kaSAqeGkqIGtvcnJhbCBvbiB2YWxlbSBqw6RyZ21pbmUgKih4aSAtIG1pbih4KSkvKG1heCh4KSAtbWluKHgpKS4qDQoNCi0gICBUZXN0aSBzYWFkdWQgZnVua3RzaW9vbmksIHZla3Rvcml0ZSBgMDoxMGAgamEgYC01OjVgIHBlYWwuIEthcyB0dWxlbXVzZWQgb24gc2FybmFzZWQgdsO1aSBlcmluZXZhZD8NCg0KYGBge3J9DQoNCnZla3Rvcl90ZWlzZW5kYSA9IGZ1bmN0aW9uKHZla3Rvcikgew0KICANCiAgdmFzdHVzID0odmVrdG9yIC0gbWluKHZla3RvcikpIC8gKG1heCh2ZWt0b3IpIC0gbWluKHZla3RvcikpDQogIA0KICByZXR1cm4odmFzdHVzKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KDQp2ZWt0b3JfdGVpc2VuZGEoMDoxMCkNCmBgYA0KDQojIyBBbmRtZXRhYmVsaWQNCg0KUHJha3RpbGlzZSBhbmRtZWFuYWzDvMO8c2kgcHVodWwgb24gYW5kbWVkIGFudHVkIHRhYmVsaXRlcywga3VzIHZlZXJndWRlcyBvbiBlcmluZXZhdCB0w7zDvHBpIHR1bm51c2VkLiBTw6TDpHJhbmUgdGFiZWwgb24gb2x1bGlzZWwga29oYWwga2EgUi1zIGt1cyBhamFsb29saXNlbHQgb24ga2FzdXRhdHVkIGBkYXRhLmZyYW1lYCBuaW1lbGlzdCBvYmpla3RpIGphIHZpaW1hc2VsIGFqYWwgcm9oa2VtIGB0aWJibGVgIG5pbWVsaXN0IGBkYXRhLmZyYW1lYCBlZGFzaWFyZW5kdXN0IGB0aWR5dmVyc2VgIHBha2V0aXN0LiBTZWxsaXNlc3QgdGFiZWxpc3QgdsO1aWIgbcO1ZWxkYSBrdWkgdHVubnVzdmVrdG9yaXRlIGtvZ3VtaXN0LCBrdXMga8O1aWsgdHVubnVzdmVrdG9yaWQgcGVhdmFkIG9sZW1hIMO8aGVwaWtrdXNlZC4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBwZWFiIHRlZ2VtYSB2YWlkIGtvcnJhIHNlc3Npb29uaSBhbGd1c2VzDQoNCm1vb2R1ZCA9IHRpYmJsZSgNCiAgTmltaSA9IGMoIk1hcmkiLCAiSmFhbmEiLCAiUGVldGVyIiksDQogIFBpa2t1cyA9IGMoMTU1LCAxNjUsIDE5MCksIA0KICBLYWFsID0gYyg1MCwgNjAsIDEwMCksIA0KICBTdWd1ID0gYygiTiIsICJOIiwgIk0iKQ0KKQ0KDQptb29kdWQNCmBgYA0KDQojIyBBbmRtZXRhYmVsaSBzaXNzZSBsdWdlbWluZSBlcmluZXZhdGVzdCBhbGxpa2F0ZXN0DQoNCiMjIyBUZWtzdGlmYWlsaWQNCg0KQW5kbWV0YWJlbGVpZCBob2l0YWtzZSBlbmFtYXN0aSB0ZWtzdGlmYWlsaWRlbmEsIGt1cyB2ZWVydWQgb24ga2FzIHRhYnVsYWF0b3JpLCBrb21hIHbDtWkgc2VtaWtvb2xvbmlnYSBlcmFsZGF0dWQuIFNlbGxpc3RlIGZhaWxpZGUgbHVnZW1pc2VrcyBzYWFiIGthc3V0YWRhIGByZWFkX2RlbGltYCBrw6Rza3UsIGt1cyB0dWxlYiBraW5kbGFzdGkgbcOkw6RyYXRhIGZhaWxpbWkgamEgdmVlcmd1ZGUgZXJhbGRhamEuDQoNCmBgYHtyfQ0KbWFzcyA9IHJlYWRfZGVsaW0oIm1hc3NhY2h1c2V0dHMuY3N2IiwgZGVsaW0gPSAiLCIpDQptYXNzDQpgYGANCg0KYGBge3J9DQptYXNzID0gcmVhZF9kZWxpbSgibWFzc2FjaHVzZXR0cy5jc3YiLCBkZWxpbSA9ICJcdCIpDQptYXNzDQpgYGANCg0KS3VuYSBlbmFtdXMgZmFpbGUgb24gZXJhbGRhdHVkIGthcyB0YWJ1bGFhdG9yaSB2w7VpIGtvbWFnYSwgc2lpcyBuZW5kZSBmYWlsaWRlIGphb2tzIG9uIGthIHNob3J0Y3V0aWQgYHJlYWRfdHN2YCBqYSBgcmVhZF9jc3ZgLg0KDQpgYGB7cn0NCm1hc3MgPSByZWFkX2NzdigibWFzc2FjaHVzZXR0cy5jc3YiKQ0KDQptYXNzDQpgYGANCg0KS2lyamVsZGF0dWQga8Okc3VkIHRlZXZhZCDDvGxkaXNlbHQgcMOkcmlzIGhlYWQgdMO2w7ZkLCBrdWlkIHNpaXNraSBvbiBmYWlsaWRlbCB0aWh0aSBvbWFww6RyYXNpZCwgbsOkaXRla3Mgb24gcHV1ZHV2YWQgdsOkw6RydHVzZWQgaW1lbGlrdWx0IG3DpHJnaXR1ZCwgbXV1dHVqYSBsb2V0YWtzZSBzaXNzZSB2YWxlIHTDvMO8YmlnYSwgam5lLiBOZW5kZSBwcm9ibGVlbWlkZWdhIG9uIHbDtWltYWxpayB0ZWdlbGVkYSBlbmFtYXN0aSBqdWJhIGByZWFkXypgIGvDpHN1IHNlZXMgcmFrZW5kYWRlcyBlcmluZXZhaWQgcGFyYW1lZXRyZWlkLiBUYXN1YiBsdWdlZGEgbmVuZGUgZnVua3RzaW9vbmlkIGFiaWxlaGVrw7xsZ2kgamEgb3RzaWRhIG7DpGl0ZWlkIGt1aSBow6R0dGEgasOkw6RiLg0KDQojIyMjIMOcbGVzYW5uZSA1DQoNCi0gICBMb2Ugc2lzc2UgYW5kbWVzdGlrIGZhaWxpc3QgbWFzc2FjaHVzZXR0cy50eHQgKHNlZSBhc3ViIHNhbWFzIGthdGFsb29naXMga3VpIHByYWt0aWt1bWkgbm90ZWJvb2spLg0KDQpgYGB7cn0NCg0KbWFzc190eHQgPSByZWFkX3RzdigibWFzc2FjaHVzZXR0cy50eHQiKQ0KDQptYXNzX3R4dA0KYGBgDQoNCiMjIyBSRGF0YQ0KDQpSIHRhYmVsaXRlIHNhbHZlc3RhbWluZSB0ZWtzdGlmYWlsaWRlbmEgbmluZyBuZWRlIGhpbGplbSBzaXNzZWx1Z2VtaW5lIHbDtWliIHDDtWhqdXN0YWRhIHZpZ3UuIEt1aSBtw7VuaSBmYWlsIGx1dWFrc2UgUi1zIGphIGhpbGplbSBrYSBSLXMgZWRhc2kgdMO2w7ZkZWxkYWtzZSBvbiBrYXN1bGlrIHRhIHNhbHZlc3RhZGEgYmluYWFyc2UgUiBvYmpla3RpbmEuIE5paSBvbiB0ZWRhIHbDpGdhIGxpaHRuZSBoaWxqZW0gc2lzc2UgbHVnZWRhLiBTZWRhIG9uIHbDtWltYWxpayBzYWF2dXRhZGEga8Okc2t1ZGVnYSBgc2F2ZWAgamEgYGxvYWRgLiBLw6RzdWxlIHNhdmUgdsO1aWIgZXR0ZSBhbmRhIGthIG1pdHUgb2JqZWt0aS4NCg0KYGBge3J9DQp4ID0gMQ0Kc2F2ZSh4LCBtYXNzLCBmaWxlID0gIm9iamVjdHMuUkRhdGEiKQ0KYGBgDQoNClNhbHZlc3RhdHVkIG9iamVrdGkgc2FhYiBzaXNzZSBsdWdlZGEga8Okc3VnYSBgbG9hZGAuIFZhaWtpbWlzaSB0ZWtpdmFkIHTDtsO2a2Vza2tvbmRhIHNhbWEgbmltZWdhIG9iamVrdGlkIG5hZ3Ugc2FpIHNhbHZlc3RhdHVkLiBFdCBhcnUgc2FhZGEgbWlzIHTDpHBzZWx0IHNpc3NlIGxvZXRpLCB0YXN1YiBrYXN1dGFkYSBwYXJhbWVldHJpdCBgdmVyYm9zZWAuDQoNCmBgYHtyfQ0KbG9hZCgib2JqZWN0cy5SRGF0YSIsIHZlcmJvc2UgPSBUKQ0KYGBgDQoNCiMjIEFuZG1ldGFiZWxpdGUgdMO2w7Z0bGVtaW5lIHRpZHl2ZXJzZSBrw6Rza3VkZWdhDQoNClItcyBvbiBtaXRtZWlkIHZpaXNlIGt1aWRhcyBhbmRtZWlkIHNhYWIgdMO2w7ZkZWxkYSwgdmlpbWFzZWwgYWphbCBvbiB2w6RnYSBwb3B1bGFhcnNla3Mgc2FhbnVkIGzDpGhlbmVtaW5lIG1pcyBvbiBpbXBsZW1lbnRlZXJpdHVkIHBha2V0dGlkZSBrb2d1bWlrdXMgw7xoaXNlIG5pbWV0YWphZ2EgYHRpZHl2ZXJzZWAuIFbDtWlrcyDDtmVsZGEsIGV0IHRlZ3Ugb24gbGF1c2Egb21hbW9vZGkgYWxhbWtlZWxlZ2EgUi1pIHNlZXMsIG1pcyBsYWVuYWIga29udHNlcHRzaW9vbmUgZXJpbnZlYXRlc3Qga2VlbHRlc3QgbmFndSBgU1FMYCBqYSBgYmFzaGAgbmluZyBwcm9vdmliIHBhbGp1ZCBvcGVyYXRzaW9vbmlkIHBhbm5hIHTDtsO2bGUgw7xoaXN0ZSBwcmludHNpaXBpZGUgcMO1aGphbC4NCg0KIyMjICVcPiUNCg0Kw5xoZWtzIHTDpGh0c2FtYWtzIGvDpHN1a3MgdGlkeXZlcnNlIHB1aHVsIG9uIG5uICJ0b3J1IiBgJT4lYCBtaXMgdsO1aW1hbGRhYiBraXJqdXRhZGEgcGlra2FzaWQga8Okc2t1ZGUgamFkYXNpZCBsb2V0YXZhbHQuIElsbHVzdHJlZXJpbWFrcyBzZWxsZSBrYXN1bGlra3VzdCB2YWF0YW1lIGrDpHJnbWlzdCBuw6RpZGV0Lg0KDQpgYGB7cn0NCnByaWNlcyA9IGMoIiQxNDIzLjU1IiwgIiQ1NTYuOTgiLCAiJDQzMjEuOTkiLCAiJDY1Ny4wMSIpDQoNCnByaWNlc190cmltID0gc3RyX3JlcGxhY2UocHJpY2VzLCAiXFwkIiwgIiIpDQpwcmljZXNfdHJpbV9udW0gPSBhcy5udW1lcmljKHByaWNlc190cmltKSANCnByaWNlc190cmltX251bV9yb3VuZCA9IHJvdW5kKHByaWNlc190cmltX251bSwgZGlnaXRzID0gMCkNCnByaWNlc19yb3VuZF9maW5hbCA9IHN0cl9jKCIkIiwgcHJpY2VzX3RyaW1fbnVtX3JvdW5kKQ0KcHJpY2VzX3JvdW5kX2ZpbmFsDQpgYGANCg0KU2lpbiByYWtlbmRhbWUgasOkcmplc3QgbWl0bWVpZCBvcGVyYXRzaW9vbmUgamEgc2FsdmVzdGFtZSBrw7VpayB2YWhlbXV1dHVqYXRlc3NlIG1pbGxlbGUgcGVhbWUgbmltZXNpZCBtw7V0bGVtYSwgbWlzIG9uIHN1aHRlbGlzZWx0IHTDvMO8dHUuIE1lIHbDtWltZSBzZWxsaXNlIG9wZXJhdHNpb29vbmkga2lyanV0YWRhIGthIMO8aGVsIHJlYWwuDQoNCmBgYHtyfQ0Kc3RyX2MoIiQiLCByb3VuZChhcy5udW1lcmljKHN0cl9yZXBsYWNlKHByaWNlcywgIlxcJCIsICIiKSksIGRpZ2l0cyA9IDApKQ0KYGBgDQoNCkt1aWQgc2VsbGlzdCBhc2phIG9uIHbDpGdhIHJhc2tlIGx1Z2VkYS4gU2VzdCBmdW5rdHNpb29uZSByYWtlbmRhdGFrc2UgasOkcmplc3Qgc2Vlc3Rwb29vbHQgdsOkbGphcG9vbGUuIFBhbGp1IGxpaHRzYW0gb2xla3MgbHVnZWRhLCBrdWkgZnVua3RzaW9vbmlkIGxpaWd1a3MgcmFrZW5kYW1pc2UgasOkcmpla29ycmFzIHZhc2FrdWx0IHBhcmVtYWxlLiBTZWUgb24ganVzdCBzZWUgbWlkYSBvcGVyYWF0b3IgYCU+JWAgdGVlYi4gVGEgdsO1aW1hbGRhYiBraXJqdXRhZGEgYGYoZyh4KSlgLCBrdWkgYHggJT4lIGcoKSAlPiUgZigpYC4gTWVpZSBlZWxtaW5lIG7DpGlkZSBuw6Rla3MgdsOkbGphIGrDpHJnbmV2Lg0KDQpgYGB7cn0NCnByaWNlcyAlPiUgc3RyX3JlcGxhY2UoIlxcJCIsICIiKSAlPiUgYXMubnVtZXJpYygpICU+JSByb3VuZCgpICU+JSBzdHJfYygiJCIsIC4pDQpgYGANCg0KUGFuZSB0w6RoZWxlLCBldCBrdWkgZWVsbmV2YSBmdW5rdHNpb29uaSB2w6RsanVuZCBwZWFrcyBvbGVtYSBqw6RyZ21pc2VzIGZ1bmt0c2lvb25pIHbDpGxqYWt1dHNlcyBlc2ltZXNlbCBwb3NpdHNpb29uaWwsIHNpaXMgdsO1aWIgc2VsbGUgw6RyYSBqw6R0dGEuIEt1aSB2w6RsanVuZCBwZWFiIG1pbmVtYSBtw7VuZWxlIHRlaXNlbGUgcG9zaXRzaW9vbmlsZSwgc2lpcyBzYWFiIHNlZGEgbcOkcmtpZGEga3VpIGAuYCAodnQgdmlpbWFuZSBrw6RzaykuDQoNCiMjIyBUaWR5dmVyc2UgZnVua3RzaW9vbmlkDQoNClRpZHl2ZXJzZSBvbiDDvGxlcyBlaGl0YXR1ZCBmdW5rdHNpb29uaWRlbCwgbWlsbGVzdCBpZ2HDvGtzIHbDtXRhYiBzaXNzZSBhbmRtZXRhYmVsaSBqYSBhbm5hYiB2w6RsamEgbW9kaWZpdHNlZXJpdHVkIGFuZG1ldGFiZWxpLiBJZ2EgZnVua3RzaW9vbiB0ZW9zdGFiIGFpbnVsdCDDvGh0ZSBvcGVyYXRzaW9vbmksIGFnYSBrdWkgbmVpZCBvcGVyYWF0b3JpZ2EgJVw+JSBqw6RyamVzdCByYWtlbmRhZGEgb24gdsO1aW1hbGlrIHNhYXZ1dGFkYSB2w6RnYSBwYWxqdS4gUGVhbWlzZWQgZnVua3RzaW9vbmlkIHRpZHl2ZXJzZSBwYWtldHRpZGVzIG9uIGrDpHJnbmV2YWQ6DQoNCi0gICAqKnNlbGVjdCoqIC0gdsO1aW1hbGRhYiB2YWxpZGEgYW5kbWV0YWJlbGkgdmVlcmdlDQoNCi0gICAqKmZpbHRlciAtKiogdsO1aW1hbGRhYiB2YWxpZGEgYW5kbWV0YWJlbGkgcmlkdQ0KDQotICAgKiptdXRhdGUqKiAtIHbDtWltYWxkYWIgdGVraXRhZGEgdGFiZWxpc3NlIHV1c2kgdmVlcmd1c2lkIHbDtWkgbW9kaWZpdHNlZXJpZGEgdmFudQ0KDQotICAgKipncm91cF9ieSoqIGphICoqc3VtbWFyaXplKiogLSB2w7VpbWFsZGF2YWQgc3VtbWVlcmlkYSB2w6TDpHJ0dXNpIHR1bm51c3RlIHBvb2x0IGRlZmluZWVyaXR1ZCBncnVwcGlkZXMNCg0KLSAgICoqYXJyYW5nZSoqIC0gdsO1aW1hbGRhYiBzb3J0ZWVyaWRhIHRhYmVsaXQgw7xoZSB2w7VpIG1pdG1lIHR1bm51c2UgasOkcmdpDQoNCkrDpHJnbmV2YWx0IHZhYXRhbWUgaWdhIGZ1bmt0c2lvb25pIGphIG3DtW5kYSBrYXN1dHVzbsOkaWRldCB0w6Rwc2VtYWx0IGthc3V0YWRlcyBhbmRtZXN0aWtrdSBgbWFzc2AsIG1pcyBzYWkgc2lzc2UgbG9ldHVkIGVlbG5ldmFsdC4NCg0KIyMjIyBzZWxlY3QNCg0KYHNlbGVjdGAgdsO1aW1hbGRhYiBhbmRtZXRhYmVsaSB2ZWVyZ2UgdmFsaWRhIGphIG5laWQga2Egc2VsbGUga8OkaWd1cyDDvG1iZXIgbmltZXRhZGEuIFZlZXJndWRlIG5pbWVkIHNhYWIgZnVua3RzaW9vbmlsZSBldHRlIGFuZGEgaWxtYSBqdXR1bcOkcmtpZGV0YS4NCg0KYGBge3J9DQptYXNzICU+JSBzZWxlY3QoQUdFUCwgU0VYKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgc2VsZWN0KEFnZSA9IEFHRVAsIEdlbmRlciA9IFNFWCkgIyBwYW5lbWUgaWx1c2FtYWQgbmltZWQNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdCgtQ0lULCAtQUdFUCkgIyBTYWFtZSBrYSBrb25rcmVldHNlaWQgdmVlcmdlIHbDpGxqYSB2aXNhdGENCmBgYA0KDQojIyMjIGZpbHRlcg0KDQpgZmlsdGVyYCB2w7VpbWFsZGFiIHJpZHUgZmlsdHJlZXJpZGEgc2VhZGVzIGxvb2dpbGlzaSB0aW5naW11c2kgdmVlcmd1ZGVsZS4gU2lzZW5kdGFiZWxpcyBvbGV2YXRlIHZlZXJndWRlIG5pbWVkIHR1bm5lYiBgZmlsdGVyYCBhdXRvbWFhdHNlbHQgw6RyYS4NCg0KYGBge3J9DQptYXNzICU+JSBmaWx0ZXIoQUdFUCA8IDIwKSAjIFZhbnVzIHbDpGlrc2VtIGt1aSAyMA0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZmlsdGVyKENJVCA9PSAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiIpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBmaWx0ZXIoDQogIENJVCANCiAgICAlaW4lIA0KICAgICAgYygNCiAgICAgICAgIlUuUy4gY2l0aXplbiBieSBuYXR1cmFsaXphdGlvbiIsIA0KICAgICAgICAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiINCiAgICAgICkNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZmlsdGVyKEFHRVAgPCAyMCB8IENJVCA9PSAiTm90IGEgY2l0aXplbiBvZiB0aGUgVS5TLiIpIA0KYGBgDQoNCiMjIyMgbXV0YXRlDQoNCmBtdXRhdGVgIHbDtWltYWxkYWIgbHV1YSB1dXNpIHZlZXJhaWQgdmFzdGF2YWx0IHNlbGxlbGUga2FzIHZlZXJnIG1pbGxlbGUgdsOkw6RydHVzIG9taXN0YXRha3NlIGp1YmEgZWtzaXRlZXJpYiB2w7VpIG1pdHRlLiBOYWd1IGVlbG5ldmF0ZWxnaSBmdW5rdHNpb2dlIHbDtWkgbXV1dGEgb2xlbWFzb2xldm9uaWRlbCBgbXV0YXRlYCBzZWVzIHNhYWIgdGVoZXRlbCBrYXN1dGFkYSB2ZWVydW5pbWVzaWQuDQoNCmBgYHtyfQ0KbWFzcyAlPiUgbXV0YXRlKE9sZCA9IEFHRVAgPiA2MCkgJT4lIHNlbGVjdChBR0VQLCBPbGQpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBtdXRhdGUoQUdFUCA9IEFHRVAgKyAxKSANCmBgYA0KDQojIyMjIHN1bW1hcml6ZQ0KDQpgc3VtbWFyaXplYCBvbiBrw6RzayBtaXMgdsO1aW1hbGRhYiBhbmRtZXN0aWt1bCBrb2trdXbDtXR0ZWlkIHbDpGxqYSBhcnZ1dGFkYS4gRXJpbmV2YWx0IGBtdXRhdGVgIGvDpHN1c3QgdGFnYXN0YWIgdGEgYWludWx0IHbDpGxqYWFydnV0YXR1ZCBzdXVydXNlZCBqYSBtaXR0ZSBtaWRhZ2kgbXV1ZC4NCg0KYGBge3J9DQptYXNzICU+JSBzdW1tYXJpemUoTWVhbkFnZSA9IG1lYW4oQUdFUCkpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBzdW1tYXJpemUoTWVhbkFnZSA9IG1lYW4oQUdFUCksIE1pbkFnZSA9IG1pbihBR0VQKSkNCmBgYA0KDQpGdW5rdHNpb29uIGBuKClgIGBzdW1tYXJpemVgIHNlZXMgw7x0bGViIGt1aSBtaXR1IHJpZGEgc2lzZW5kIHRhYmVsaXMgb24uDQoNCmBgYHtyfQ0KbWFzcyAlPiUgc3VtbWFyaXplKE1lYW5BZ2UgPSBtZWFuKEFHRVApLCBNaW5BZ2UgPSBtaW4oQUdFUCksIE4gPSBuKCkpDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSANCiAgc3VtbWFyaXplKA0KICAgIE1lYW5BZ2UgPSBtZWFuKEFHRVApLCANCiAgICBNaW5BZ2UgPSBtaW4oQUdFUCksIA0KICAgIE1heEFnZSA9IG1heChBR0VQKSwgDQogICAgTiA9IG4oKQ0KICApOw0KYGBgDQoNCiMjIyMgZ3JvdXBfYnkNCg0KYGdyb3VwX2J5YCB2w7VpbWFsZGFiIHJha2VuZGFkYSBubiAiKnNwbGl0LWFwcGx5LWNvbWJpbmUqInN0cmF0ZWVnaWF0LCBrdXMgYW5kbWVzdGlrIHTDvGtlbGRhdGFrc2Ugw7xoZSB2w7VpIG1pdG1lIHR1bm51c2UgdsOkw6RydHVzIGFsdXNlbCBhbGFtYW5kbWVzdGlrZWtzLCByYWtlbmRhdGFrc2UgbWluZ2l0IGZ1bmt0c2lvb25pIGFsYW0tYW5kbWVzdGlrZWwgbmluZyB0dWxlbXVzZWQga29tYmluZWVyaXRha3NlLiBLdWkgbWUgb2xlbWUgcmFrZW5kYW51ZCBrw6Rza3UgZ3JvdXBfYnkgYW5kbWVzdGlrdWxlIHNpaXMgasOkcmduZXZhdGUga8Okc2t1ZGUgcHVodWwganVzdCBzZWxsaW5lIG9wZXJhdHNpb29uIHRvaW11YmtpLg0KDQpgZ3JvdXBfYnlgIGphIGBzdW1tYXJpemVgIG1vb2R1c3RhdmFkIGtvbWJpbmF0c2lvb25pLCBtaWxsZWdhIG9uIHbDtWltYWxpayB0dW5udXN0ZSBwb29sdCBkZWZpbmVlcml0dWQgZ3J1cHBpZGUga2F1cGEgc3VtbWVlcmlkYSB0ZWlzdGUgbXV1dHVqYXRlIHbDpMOkcnR1c2VpZC4gVHVsZW11c3NlIGrDpMOkdmFkIGFsbGVzIHR1bm51c2VkIG1pbGxlIGrDpHJnaSBncnVwZWVyaXRpIG5pbmcgYHN1bW1hcml6ZWAga8Okc3VzIGRlZmluZWVlcml0dWQgdHVubnVzZWQuIEdydXBlZXJpZGEgdsO1aWIgbmlpIMO8aGUga3VpIG1pdG1lIHR1bm51c2UgasOkcmdpLg0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCkgJT4lIHN1bW1hcml6ZShBR0VQID0gbWVhbihBR0VQKSkNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCwgU0VYKSAlPiUgc3VtbWFyaXplKEFHRVAgPSBtZWFuKEFHRVApKQ0KYGBgDQoNCkZ1bmt0c2lvb24gYG4oKWAgYHN1bW1hcml6ZWAgc2VlcyDDvHRsZWIga3VpIG1pdHUgdGFiZWxpIHJpZGEga29ua3JlZXRzZWxlIGdydXBlZXJpdmF0ZSB0dW5udXN0ZSBrb21iaW5hdHNpb29uaWxlIHZhc3RhYi4gU2VlIG9uIHbDpGdhIGthc3VsaWsgc2FnZWR1c3RhYmVsaXRlIHRlZ2VtaXNlbA0KDQpgYGB7cn0NCm1hc3MgJT4lIGdyb3VwX2J5KENJVCkgJT4lIHN1bW1hcml6ZShOID0gbigpKQ0KYGBgDQoNCmBgYHtyfQ0KbWFzcyAlPiUgZ3JvdXBfYnkoQ0lULCBTRVgpICU+JSBzdW1tYXJpemUoQUdFUCA9IG1lYW4oQUdFUCksIE4gPSBuKCkpDQpgYGANCg0KS3VpIHDDpHJhc3QgZ3JvdXBfYnkga8Okc2t1IHJha2VuZGEgbXV0YXRlIGvDpHNrdS4gU2lpcyBtdXRhdGUgdMO2w7Z0YWIganVwaWthdXBhIGdyb3VwX2J5IGRlZmluZWVyaXR1ZCBhbGFtaHVsa2FkZWwuIE7DpGl0ZWtzIG5paSBzYWFtZSBsaXNhZGEgaWdhbGUgcmVhbGUgZ3J1cGlrZXNrbWlzZSB2w7VpIGrDpHJqZWtvcnJhIG51bWJyaSBncnVwaSBzZWVzLg0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdChBR0VQLCBTRVgpICU+JSBncm91cF9ieShTRVgpICU+JSBtdXRhdGUoTWVhbkFnZUdyb3VwID0gbWVhbihBR0VQKSkNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIHNlbGVjdChBR0VQLCBTRVgpICU+JSBncm91cF9ieShTRVgpICU+JSBtdXRhdGUoSWRJbkdyb3VwID0gMTpuKCkpDQpgYGANCg0KIyMjIyBhcnJhbmdlDQoNCmBhcnJhbmdlYCBsaWh0c2FsdCBzb3J0ZWVyaWIgYW5kbWV0YWJlbGkgZXR0ZWFudHVkIHR1bm51cyh0KWUgasOkcmdpLg0KDQpgYGB7cn0NCm1hc3MgJT4lIGFycmFuZ2UoQUdFUCkgIyBWYWlraW1pc2UgdsOkaWtzZW1hc3Qgc3V1cmVtYWtzDQpgYGANCg0KYGBge3J9DQptYXNzICU+JSBzZWxlY3QoQUdFUCwgU0VYKSAlPiUgYXJyYW5nZShBR0VQLCBTRVgpDQpgYGANCg0KYGBge3J9DQojIG1hc3MgJT4lIGFycmFuZ2UoZGVzYyhBR0VQKSkgIyBLw6RzdWdhIGRlc2Mgc2FhYiBzb3J0ZWVyaW1pc2Ugc3V1bmEgw7xtYmVyIHDDtsO2cmF0YQ0KDQptYXNzICU+JSBhcnJhbmdlKC1BR0VQKSAjIEvDpHN1Z2EgZGVzYyBzYWFiIHNvcnRlZXJpbWlzZSBzdXVuYSDDvG1iZXIgcMO2w7ZyYXRhDQpgYGANCg0KIyMjIEZ1bmt0c2lvb25pZGUga29tYmluZWVyaW1pbmUNCg0KT2xndWdpLCBldCBpZ2EgZnVua3RzaW9vbiDDvGtzaSB0ZW9zdGFiIGtvbmtyZWV0c2UgbGlodHNhIG9wZXJhdHNpb29uaSwgdsO1aWIgbmVpZCDDvGtzdGVpc2UgasOkcmVsIHJpdHRhIGxhZHVkZXMgbGFoZW5kYWRhIHDDpHJpcyBrZWVydWthaWQgcHJvYmxlZW1lLiBOw6RpdGVrcyBzaWluIGxlaWFtZSB0b3AgNSBhbWV0aXQgdMO2w7ZlYWxpc3RlbGUgVVNBcyBqYSB2w6RsamFzcG9vbCBzw7xuZGludWQgaW5pbWVzdGVsZS4NCg0KYGBge3J9DQptYXNzICU+JSANCiAgc2VsZWN0KEFnZSA9IEFHRVAsIENpdGl6ZW5zaGlwID0gQ0lULCBPY2N1cGF0aW9uID0gT0NDUCkgJT4lIA0KICBmaWx0ZXIoQWdlID4gMTggJiBBZ2UgPCA2NSkgJT4lIA0KICBtdXRhdGUoQm9yblVTID0gQ2l0aXplbnNoaXAgPT0gIkJvcm4gaW4gdGhlIFUuUy4iKSAlPiUgDQogIGdyb3VwX2J5KEJvcm5VUywgT2NjdXBhdGlvbikgJT4lIA0KICBzdW1tYXJpemUoTiA9IG4oKSkgJT4lIA0KICBncm91cF9ieShCb3JuVVMpICU+JSANCiAgbXV0YXRlKFJhbmsgPSByYW5rKC1OKSkgJT4lIA0KICBmaWx0ZXIoUmFuayA8PSA1KSAlPiUgDQogIGFycmFuZ2UoQm9yblVTLCBSYW5rKQ0KYGBgDQoNCiMjIyDDnGxlc2FuZGVkIDYNCg0KLSAgIEt1bW1hbCBvbiBrw7VyZ2VtIGtlc2ttaW5lIHBhbGsga2FzIMO8bGUgdsO1aSBhbGxhIDU1IGFhc3Rhc3RlbCBhcnN0aWRlbD8gKEthc3V0YSB0dW5udXNlaWQgV0FHUCAtIHBhbGssIEFHRVAgLSB2YW51cywgT0NDUCAtIGFtZXQsIGthdGVnb29yaWEgIk1FRC1QSFlTSUNJQU5TIEFORCBTVVJHRU9OUyIsIFcpDQoNCi0gICBNaWxsaXNlIGFtZXRlaWQgZXNpbmRhdmFkIG5haXNlZCB0w7bDtnRhdmFkIGtlc2ttaXNlbHQgbsOkZGFsYXMga8O1aWdlIGthdWVtPyBLZXNrZW5kdSBhaW51bHQgYW1ldGl0ZWxlLCBtaWRhIGVzaW5kYXZhaWQgbmFpc2kgb24gYW5kbWVzdGlrdXMgdsOkaGVtYWwgMTAuIChLYXN1dGEgdHVubnVzZWlkIEFHRVAgLSB2YW51cywgU0VYIC0gc3VndSB2w6TDpHJ0dXMgIkZlbWFsZSIgLCBXS0hQIC0gbsOkZGFsYXMgdGVodHVkIHTDtsO2dHVubmlkLCBPQ0NQIC0gYW1ldCkNCg0KLSAgIE1pcyBvbiB0w6Rpc2thc3ZhbnVkIGluaW1lc3RlIGvDtWlnZSBrw7VyZ2VtYSBrZXNrbWlzZSBwYWxnYWdhIGFtZXRpZCBNYXNzYWNodXNldHRzaSBhbmRtZXN0aWt1IGtvaGFzZWx0PyBLZXNrZW5kdSBhaW51bHQgYW1ldGl0ZWxlLCBtaWxsZSBlc2luZGFqYWlkIG9uIGFuZG1lc3Rpa3VzIHbDpGhlbWFsIDEwLiAoS2FzdXRhIHR1bm51c2VpZCBBR0VQIC0gdmFudXMsIFdBR1AgLSBwYWxrLCBPQ0NQIC0gYW1ldCkNCg0KYGBge3J9DQoNCm1hc3MgJT4lIA0KICBzZWxlY3QoQWdlID0gQUdFUCwgT2NjdXBhdGlvbiA9IE9DQ1AsIFdhZ2UgPSBXQUdQKSAlPiUgDQogIGZpbHRlcihPY2N1cGF0aW9uID09ICJNRUQtUEhZU0lDSUFOUyBBTkQgU1VSR0VPTlMiKSAlPiUgDQogIG11dGF0ZShpc092ZXI1NSA9IEFnZSA+PSA1NSkgJT4lIA0KICBncm91cF9ieShpc092ZXI1NSwgT2NjdXBhdGlvbikgJT4lIA0KICBzdW1tYXJpc2UoTWVhbldhZ2UgPSBtZWFuKFdhZ2UpKQ0KYGBgDQoNCmBgYHtyfQ0KDQptYXNzICU+JSANCiAgc2VsZWN0KHNleCA9IFNFWCwgb2NjdXBhdGlvbiA9IE9DQ1AsIHdvcmtIb3Vyc1BlcldlZWsgPSBXS0hQKSAlPiUNCiAgZmlsdGVyKHNleCA9PSAiRmVtYWxlIikgJT4lIA0KICBncm91cF9ieShvY2N1cGF0aW9uKSAlPiUNCiAgZmlsdGVyKG4oKSA+PSAxMCkgJT4lDQogIHN1bW1hcmlzZShtZWFuSG91cnMgPSBtZWFuKHdvcmtIb3Vyc1BlcldlZWspKSAlPiUgIyBtZWFuKHdvcmtIb3Vyc1BlcldlZWssIG5hLnJtID0gVFJVRSkNCiAgYXJyYW5nZSgtbWVhbkhvdXJzKSAlPiUNCiAgc2VsZWN0KG9jY3VwYXRpb24sIG1lYW5Ib3VycykNCmBgYA0KDQpgYGB7cn0NCm1hc3MgJT4lIA0KICBmaWx0ZXIoU0VYID09ICJGZW1hbGUiKSAlPiUgDQogIGdyb3VwX2J5KE9DQ1ApICU+JSANCiAgZmlsdGVyKG4oKSA+PSAxMCkgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9ob3VycyA9IG1lYW4oV0tIUCwgbmEucm0gPSBUUlVFKSwNCiAgICBjb3VudCA9IG4oKQ0KICApICU+JSANCiAgYXJyYW5nZShkZXNjKG1lYW5faG91cnMpKQ0KYGBgDQoNCiMgTGlzYW1hdGVyamFsZQ0KDQotICAgUHl0aG9uaSBzeW50YWtzaSB2w7VyZGx1cyBSLWdhOiA8aHR0cHM6Ly9wYW5kYXMucHlkYXRhLm9yZy9wYW5kYXMtZG9jcy9zdGFibGUvZ2V0dGluZ19zdGFydGVkL2NvbXBhcmlzb24vY29tcGFyaXNvbl93aXRoX3IuaHRtbD4NCg0KLSAgIFIgdHV0b3JpYWwgQW5kbWVrYWV2ZSBrdXJzdXNlc3Q6IDxodHRwczovL2NvdXJzZXMuY3MudXQuZWUvMjAxNy9ETS9mYWxsL01haW4vUlR1dG9yaWFsPg0K